package com.joeshing.security.context;

import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Date;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper;
import org.springframework.security.web.context.SecurityContextRepository;

import com.joeshing.cache.Cache;
import com.joeshing.cache.McCache;
import com.joeshing.security.authentication.SessionAuthenticationDetails;

public class CacheSecurityContextRepository implements SecurityContextRepository {
	
	protected final Log logger = LogFactory.getLog(this.getClass());
	private Cache cache;
	private String name = "ContextCache";
	private Integer expiry = 1800;
	private boolean disableUrlRewriting = false;
	
	private String sessionIdKey = "__security_cookie_session_id_key";

	@Override
	public boolean containsContext(HttpServletRequest request) {
		String sessionId = getSessionId(request);
		if(sessionId!=null){
			Authentication authentication = (Authentication)cache.get(sessionId);
			if(authentication!=null)
				return true;
		}
		return false;
	}

	@Override
	public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
		SecurityContext context = new SecurityContextImpl();
		HttpServletRequest request = getRequest(requestResponseHolder);
		HttpServletResponse response = getResponse(requestResponseHolder);
		if(request!=null&&response!=null){
			String sessionId = getSessionId(request);
			if(sessionId!=null){
				Authentication authentication = (Authentication)cache.get(sessionId);
				if(authentication!=null){
					SessionAuthenticationDetails details = (SessionAuthenticationDetails)authentication.getDetails();
					if(details!=null)
						details.setRemoteAddress(request.getRemoteAddr());
				}
				context.setAuthentication(authentication);
			}
			SaveToCacheResponseWrapper responseWrapper = new SaveToCacheResponseWrapper(response);
			setResponse(requestResponseHolder, responseWrapper);
		}
		return context;
	}

	@Override
	public void saveContext(SecurityContext context,HttpServletRequest request, HttpServletResponse response) {
		SaveToCacheResponseWrapper responseWrapper = (SaveToCacheResponseWrapper)response;
        if (!responseWrapper.isContextSaved() ) {
            responseWrapper.saveContext(context);
        }
		
	}

	public void setCache(Cache cache) {
		McCache mcCache = new McCache();
		mcCache.setName(name);
		mcCache.setCache(cache);
		this.cache = mcCache;
	}

	public void setExpiry(Integer expiry) {
		this.expiry = expiry;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void clearUserContext(String userName){
		String sessionId = (String)cache.get(userName);
		if(sessionId!=null){
			cache.remove(userName);
			cache.remove(sessionId);
		}
	}
	
	private String getSessionId(HttpServletRequest request) {
		String sessionId = null;
		Cookie[] cookies = request.getCookies();
		if(cookies!=null){
			for(Cookie cookie : cookies)
				if(cookie.getName().equals(sessionIdKey)){
					sessionId = cookie.getValue();
					break;
				}
		}
		return sessionId;
	}
	
	private HttpServletRequest getRequest(HttpRequestResponseHolder requestResponseHolder){
		try {
			Method getRequest = HttpRequestResponseHolder.class.getDeclaredMethod("getRequest");
			getRequest.setAccessible(true);
			HttpServletRequest request = (HttpServletRequest)getRequest.invoke(requestResponseHolder);
			return request;
		} catch (Exception e) {
			logger.error("getRequest", e);
			// TODO: handle exception
			return null;
		}
	}
	
	private HttpServletResponse getResponse(HttpRequestResponseHolder requestResponseHolder){
		try {
			Method getResponse = HttpRequestResponseHolder.class.getDeclaredMethod("getResponse");
			getResponse.setAccessible(true);
			HttpServletResponse response = (HttpServletResponse)getResponse.invoke(requestResponseHolder);
			return response;
		} catch (Exception e) {
			logger.error("getResponse", e);
			return null;
		}
	}
	
	private void setResponse(HttpRequestResponseHolder requestResponseHolder,HttpServletResponse response){
		try {
			Method setResponse = HttpRequestResponseHolder.class.getDeclaredMethod("setResponse",HttpServletResponse.class);
			setResponse.setAccessible(true);
			setResponse.invoke(requestResponseHolder,response);
		} catch (Exception e) {
			logger.error("setResponse", e);
		}
	}
	
    //~ Inner Classes ==================================================================================================

    final class SaveToCacheResponseWrapper extends SaveContextOnUpdateOrErrorResponseWrapper {


        SaveToCacheResponseWrapper(HttpServletResponse response) {
            super(response, disableUrlRewriting);
        }

        @Override
        protected void saveContext(SecurityContext context) {
           if (context.getAuthentication()!=null) {
        	   SessionAuthenticationDetails details = (SessionAuthenticationDetails)context.getAuthentication().getDetails();
        	   if(details!=null){
        		   String sessionId = details.getSessionId();
        		   if(sessionId!=null){
        			   saveSessionIdInCookie(sessionId);
        			   Integer sessionTimeOut = details.getSessionTimeOut();
        			   if(sessionTimeOut==null)
        				   sessionTimeOut = expiry;
        			   Calendar calendar = Calendar.getInstance();
        			   calendar.setTime(new Date());
        			   calendar.add(Calendar.SECOND, sessionTimeOut);
        			   cache.put(context.getAuthentication().getName(), sessionId,calendar.getTime());
        			   cache.put(sessionId, context.getAuthentication(),calendar.getTime());
        		   }
        		   
        	   }
           }
        }
        
        public void saveSessionIdInCookie(){
        	saveSessionIdInCookie(CacheSessionManager.getSessionId());

        }
        
        private void saveSessionIdInCookie(String sessionId){
    		Cookie sessionIdCookie = new Cookie(sessionIdKey,sessionId);
    		sessionIdCookie.setPath("/");
    		this.addCookie(sessionIdCookie);
        }

    }

}
